Skip to main content

Pesquisa — AS-IS: NewSellStepper e arquitetura de steps do fluxo de venda

Data: 2026-05-27
Contexto: Task #19323 — Atualizar componente de steps do fluxo de venda para step Upsell condicional


Estado Atual do Código

Componente NewSellStepper

Arquivo: coezzion_vendas_app/lib/screens/sell/new_sell/widgets/new_sell_stepper.dart

  • Classe: NewSellStepper extends StatelessWidget — puramente apresentacional, sem estado reativo.
  • Construtor: const NewSellStepper(this.listStatus, {super.key}) — recebe List<StatusStepp> com exatamente 3 posições.
  • Renderização: Row com 3 _ItemStepp intercalados por 2 _SteppDivider. Cada _ItemStepp renderiza um círculo com:
    • Ícone de check (verde) se StatusStepp.complete
    • Círculo preenchido com número se StatusStepp.selected
    • Círculo com borda se StatusStepp.unselected
  • Controllers: Zero dependências. Não acessa getIt. Não usa AppGetBuilder.
  • Dependências: StepperModel, StatusStepp, SteppSell, PhosphorIcons, ZZColors, ZZTextNew, ColorThemeUtils.

Enums e modelos

Arquivo: coezzion_vendas_app/lib/shared/enum/stepp_sell.dart

enum SteppSell { produto, cliente, conferencia }

final stepperSellProduct = [StatusStepp.selected, StatusStepp.unselected, StatusStepp.unselected];
final stepperSellCustomer = [StatusStepp.complete, StatusStepp.selected, StatusStepp.unselected];
final stepperSellCheck = [StatusStepp.complete, StatusStepp.complete, StatusStepp.selected];

Arquivo: coezzion_vendas_app/lib/shared/enum/status_stepp.dart

enum StatusStepp { complete, selected, unselected }

Arquivo: coezzion_vendas_app/lib/models/stepper/stepper.dart

class StepperModel {
final int number;
final String title;
final StatusStepp status;
final SteppSell steppSell;

factory StepperModel.productStepperModel(status) => StepperModel(number: 1, title: 'Produtos', ...);
factory StepperModel.clienteStepperModel(status) => StepperModel(number: 2, title: 'Cliente', ...);
factory StepperModel.conferenciaStepperModel(status) => StepperModel(number: 3, title: 'Conferir', ...);
}

Consumo do stepper — 7 telas

TelaRotaArray passadoQuando
NewSellScreen/new_sellstepperSellProduct ou stepperSellCustomerNewSellType de Get.arguments
AddProductsScreen/add_productsstepperSellProductSempre (dentro de AppGetBuilder<ProductController>)
CheckProductsScreen/check_productsstepperSellProductCondicional (!checkSell)
AddCustomerScreen/add_sell_customerstepperSellCustomerSempre (dentro de PagingListener)
CheckCustomerScreen/check_customerstepperSellCustomerSempre
CheckAddressCustomerScreen/check_customer_addressstepperSellCustomerCondicional (!checkSell)
SelectDeliveryMethodScreen/select_delivery_methodstepperSellCustomerSempre
CheckSellScreen/check_sellstepperSellCheckSempre (dentro de AppGetBuilder<CartController>)

Observação: O stepper NÃO é reativo. A ilusão de mudança entre steps vem exclusivamente da navegação entre telas — cada tela instancia seu próprio NewSellStepper com um array estático diferente. O estado do stepper é determinado por rota, não por estado observável.

Mecanismo de reatividade do projeto

O projeto usa GetX com a seguinte cadeia reativa:

  1. OpenSalesController.activeCartDTORxn<CartDTO?> (fonte da verdade)
  2. CartController — subscreve-se via addListener e chama update() (GetxController)
  3. AppGetBuilder<T> — widget customizado que subscreve-se ao controller e chama setState()

Mecanismo de feature flag

  • Serviço: FirebaseRemoteConfigService — classe abstrata com membros estáticos
  • Acesso: FirebaseRemoteConfigService.dictionary.algumaFlag — síncrono, sem DI
  • Enum: FirebaseRemoteConfigKeysEnum — ~48 flags existentes
  • Pattern: {flag} (bool) + {flag}_by_code_store (List<String> separado por ;) para rollout gradual por loja
  • Flags planejadas (task 07): enableUpsellRecommendation + enableUpsellRecommendationByCodeStore — ainda não implementadas no código

Determinação de saleEcommerce

Arquivo: coezzion_vendas_app/lib/models/cart/cart_dto.dart (linha 385)

CartDTO addItem({Product? product, ...}) {
final saleEcommerceT = saleEcommerce ?? product.sizesStockStore.isEmpty;
return copyWith(saleEcommerce: Value(saleEcommerceT), ...);
}
  • Lock-in: ?? — uma vez definido, nunca muda até o carrinho esvaziar
  • null: carrinho vazio
  • false: estoque loja (fluxo store — candidato a upsell)
  • true: ecommerce ("Prateleira infinita" — NÃO candidato a upsell)

Fluxo de navegação entre steps

StructureSellerController.onTapNewSell()
└── OpenSalesController.createNewCartDTO(saleEcommerce: null)
└── Get.toNamed(AppRoutes.newSell, arguments: NewSellType.product)
└── NewSellScreen (stepperSellProduct)
├── "Bipar produto" → AddProductsScreen (stepperSellProduct)
│ └── "Conferir" → CheckProductsScreen (stepperSellProduct, !checkSell)
│ └── "Adicionar cliente" → NewSellScreen (stepperSellCustomer)
│ └── "Buscar cliente" → AddCustomerScreen (stepperSellCustomer)
│ └── Select → CheckCustomerScreen (stepperSellCustomer)
│ └── "Avançar" → CheckAddressCustomerScreen (stepperSellCustomer, !checkSell)
│ └── SelectDeliveryMethodScreen (stepperSellCustomer)
│ └── CheckSellScreen (stepperSellCheck)

Decisões tomadas

#QuestãoDecisão
Q1O stepper atual é reativo?Não — é puramente estático, cada tela renderiza seu próprio widget com array fixo. A mudança visual vem da navegação entre telas.
Q2Onde travar saleEcommerce?No CartDTO.addItem() via ?? — primeiro item define o tipo do carrinho. Não muda até o carrinho esvaziar.
Q3Como as feature flags são acessadas?Estaticamente via FirebaseRemoteConfigService.dictionary.* — síncrono, sem DI. Padrão estabelecido em 12+ controllers existentes.
Q4Qual o mecanismo de DI?GetIt (get_it package) com wrappers customizados registerLazySingleton e registerFactory. Controllers compartilhados usam registerLazySingleton.
Q5Quantas telas consomem o stepper?7 telas em lib/screens/sell/new_sell/. Nenhuma delas usa estado reativo para o stepper — todas passam arrays estáticos.
Q6CartController tem capacidade para mais responsabilidade?Não — 768 linhas, 55+ métodos, já gerencia produto, CRM, desconto, frete, estoque, cliente, delivery, shipment, validação de limites.